package me.ilich.piet.interpreter;

import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

import junit.framework.TestCase;
import me.ilich.piet.interpreter.Stack;
import me.ilich.piet.interpreter.StackImpl;
import me.ilich.piet.interpreter.StackState;
import me.ilich.piet.interpreter.commands.AddCommand;
import me.ilich.piet.interpreter.commands.DivideCommand;
import me.ilich.piet.interpreter.commands.DuplicateCommand;
import me.ilich.piet.interpreter.commands.GreaterCommand;
import me.ilich.piet.interpreter.commands.InCharCommand;
import me.ilich.piet.interpreter.commands.InNumberCommand;
import me.ilich.piet.interpreter.commands.ModCommand;
import me.ilich.piet.interpreter.commands.MultiplyCommand;
import me.ilich.piet.interpreter.commands.NotCommand;
import me.ilich.piet.interpreter.commands.OutCharCommand;
import me.ilich.piet.interpreter.commands.OutNumberCommand;
import me.ilich.piet.interpreter.commands.PointerCommand;
import me.ilich.piet.interpreter.commands.PopCommand;
import me.ilich.piet.interpreter.commands.PushCommand;
import me.ilich.piet.interpreter.commands.RollCommand;
import me.ilich.piet.interpreter.commands.SubtractCommand;
import me.ilich.piet.interpreter.commands.SwitchCommand;

public class StackTestCase extends TestCase {
	
	private StackImpl mStack;
	
	@Override
	protected void setUp() throws Exception {
		super.setUp();
		mStack = new StackImpl();
	}
	
	private void assertStackSize(int n){
		assertEquals(n, mStack.getStackState().getData().size());
	}
	
	private void assertStackElement(int value, int pos) {
		assertEquals(value, mStack.getStackState().getData().get(pos).intValue());
	}
	
	private void fillupStack(int[] stack){
		mStack.getStackState().getData().clear();
		for(int i=0; i<stack.length; i++){
			mStack.getStackState().getData().add(Integer.valueOf(stack[i]));
		}
	}
	
	private void assertStack(int[] stack){
		assertEquals(stack.length, mStack.getStackState().getData().size());
		for(int i=0; i<stack.length; i++){
			assertEquals("pos " + i, stack[i], mStack.getStackState().getData().get(i).intValue());
		}		
	}
	
	public void testPush(){
		assertStackSize(0);
		int value = new Random().nextInt();
		mStack.execute(new PushCommand(value));
		assertStackSize(1);
		assertStackElement(value, 0);
	}
	
	public void testPop(){
		assertStackSize(0);
		mStack.execute(new PopCommand());
		assertStackSize(0);
		mStack.getStackState().getData().add(Integer.valueOf(1));
		assertStackSize(1);
		mStack.execute(new PopCommand());
		assertStackSize(0);
	}
	
	public void testAdd() {
		assertStackSize(0);
		int v1 = new Random().nextInt();
		int v2 = new Random().nextInt();
		mStack.execute(new AddCommand());
		assertStackSize(0);
		mStack.getStackState().getData().add(Integer.valueOf(v1));
		mStack.execute(new AddCommand());
		assertStackSize(1);
		mStack.getStackState().getData().add(Integer.valueOf(v2));
		assertStackSize(2);
		mStack.execute(new AddCommand());
		assertStackSize(1);
		assertStackElement(v1+v2, 0);
	}
	
	public void testSubtract(){
		assertStackSize(0);
		final int v1 = new Random().nextInt();
		final int v2 = new Random().nextInt();
		mStack.getStackState().getData().add(Integer.valueOf(v1));
		mStack.getStackState().getData().add(Integer.valueOf(v2));
		mStack.execute(new SubtractCommand());
		assertStackSize(1);
		assertEquals(v1-v2, mStack.getStackState().getData().get(0).intValue());
	}
	
	public void testMultiply(){
		assertStackSize(0);
		
		final int v1 = new Random().nextInt();
		final int v2 = new Random().nextInt();
		mStack.getStackState().getData().add(Integer.valueOf(v1));		
		mStack.getStackState().getData().add(Integer.valueOf(v2));
		
		mStack.execute(new MultiplyCommand());
		assertStackSize(1);
		assertStackElement(v1*v2, 0);
	}
	
	public void testDivide(){	
		assertStackSize(0);
		
		final int v1 = 15;
		final int v2 = 7;
		mStack.getStackState().getData().add(Integer.valueOf(v1));		
		mStack.getStackState().getData().add(Integer.valueOf(v2));
		
		mStack.execute(new DivideCommand());
		assertStackSize(1);
		assertStackElement(2, 0);		
	}
	
	public void testMod(){
		assertStackSize(0);
		mStack.execute(new MultiplyCommand());
		
		final int v1 = 13;
		final int v2 = 5;
		mStack.getStackState().getData().add(Integer.valueOf(v1));		
		mStack.getStackState().getData().add(Integer.valueOf(v2));
		
		mStack.execute(new ModCommand());
		assertStackSize(1);
		assertStackElement(3, 0);
	}
	
	public void testNot(){
		assertStackSize(0);
		mStack.execute(new MultiplyCommand());
		
		final int v1 = new Random().nextInt();
		mStack.getStackState().getData().add(Integer.valueOf(v1));		
		
		mStack.execute(new NotCommand());
		assertStackSize(1);
		assertStackElement(v1!=0 ? 0 : 1, 0);
	}
	
	public void testGreater(){
		assertStackSize(0);
		final int v1 = new Random().nextInt();
		final int v2 = new Random().nextInt();
		mStack.getStackState().getData().add(Integer.valueOf(v2));		
		mStack.getStackState().getData().add(Integer.valueOf(v1));
		assertStackSize(2);
		mStack.execute(new GreaterCommand());
		assertStackSize(1);
		assertStackElement(v2>v1 ? 1 : 0, 0);
	}
	
	public void testPointer(){
		assertStackSize(0);
		final int v1 = new Random().nextInt(10);
		mStack.getStackState().getData().add(Integer.valueOf(v1));
		mStack.execute(new PointerCommand());
		assertStackSize(0);
		mStack.getStackState().getDP();
	}
	
	public void testSwitch(){
		assertStackSize(0);
		final int v1 = 3;
		mStack.getStackState().getData().add(Integer.valueOf(v1));
		mStack.getStackState().setCC(StackState.LEFT);
		mStack.execute(new SwitchCommand());		
		assertStackSize(0);
		assertEquals(StackState.RIGHT, mStack.getStackState().getCC());
		
		final int v2 = 4;
		mStack.getStackState().getData().add(Integer.valueOf(v2));
		mStack.getStackState().setCC(StackState.LEFT);
		mStack.execute(new SwitchCommand());		
		assertStackSize(0);
		assertEquals(StackState.LEFT, mStack.getStackState().getCC());	
	}
	
	public void testDuplicate(){
		assertStackSize(0);
		final int v1 = new Random().nextInt();
		mStack.getStackState().getData().add(Integer.valueOf(v1));
		assertStackSize(1);
		mStack.execute(new DuplicateCommand());
		assertStackSize(2);
		assertStackElement(v1, 0);
		assertStackElement(v1, 1);
	}
		
	public void testInNumber(){
		assertStackSize(0);
		final int v1 = new Random().nextInt();
		mStack.setOnInListener(new Stack.OnInListener() {
			
			@Override
			public int onInNumber() {
				return v1;
			}
			
			@Override
			public char onInChar() {
				assertTrue(false);
				return 0;
			}
		});
		mStack.execute(new InNumberCommand());
		assertStackSize(1);
		assertStackElement(v1, 0);
	}
	
	public void testInChar(){
		assertStackSize(0);
		final char v1 = 'z';
		mStack.setOnInListener(new Stack.OnInListener() {
			
			@Override
			public int onInNumber() {
				assertTrue(false);
				return v1;
			}
			
			@Override
			public char onInChar() {				
				return v1;
			}
		});
		mStack.execute(new InCharCommand());
		assertStackSize(1);
		assertStackElement(v1, 0);
	}
	
	public void testOutNumber(){
		assertStackSize(0);
		final AtomicInteger out = new AtomicInteger(-1);
		mStack.setOnOutListener(new Stack.OnOutListener() {
			
			@Override
			public void onOut(int i) {
				out.set(i);
			}
			
			@Override
			public void onOut(char c) {
				assertFalse(true);
			}
		});
		mStack.execute(new OutNumberCommand());
		assertEquals(-1, out.get());
		mStack.getStackState().getData().add(Integer.valueOf(1));
		assertStackSize(1);
		mStack.execute(new OutNumberCommand());
		assertEquals(1, out.get());
		assertStackSize(0);		
	}
	
	public void testOutChar(){
		assertStackSize(0);
		final AtomicInteger out = new AtomicInteger(-1);
		mStack.setOnOutListener(new Stack.OnOutListener() {
			
			@Override
			public void onOut(int i) {
				assertFalse(true);
			}
			
			@Override
			public void onOut(char c) {
				out.set(c);
			}
		});
		mStack.execute(new OutCharCommand());
		assertEquals(-1, out.get());
		mStack.getStackState().getData().add(Integer.valueOf(1));
		assertStackSize(1);
		mStack.execute(new OutCharCommand());
		assertEquals(1, out.get());
		assertStackSize(0);		
	}
	
	public void test1(){
		fillupStack(new int[]{2});
		mStack.execute(new DuplicateCommand());
		assertStack(new int[]{2,2});
	}
	
	public void test2(){
		fillupStack(new int[]{10, 100, 100,   2,   4});
		mStack.execute(new MultiplyCommand());
		assertStack(new int[]{10, 100, 100,   8});
	}
	
	public void testRoll1(){
		fillupStack(new int[]{1, 2, 3, 4, 4, 5, 3, 2});
		mStack.execute(new RollCommand());
		assertStack(new int[]{1, 2, 3,   4, 5, 4});
	}
	
	public void testRoll3(){
		fillupStack(new int[]{1, 2, 3, 4, 5, 6, 0, 0});
		mStack.execute(new RollCommand());
		assertStack(new int[]{1, 2, 3, 4, 5, 6});
	}
	
	public void testRoll4(){
		fillupStack(new int[]{1, 2, 3, 4, 5, 6, 2, 2});
		mStack.execute(new RollCommand());
		assertStack(new int[]{1, 2, 3, 4, 5, 6});
	}
	
	public void testRoll5(){
		fillupStack(   new int[]{0, 1, 2, 3, 4, 5, 2, 1});
		mStack.execute(new RollCommand());
		assertStack(new int[]{0, 1, 2, 3, 5, 4});
	}
	
	public void testRoll6(){
		fillupStack(   new int[]{0, 1, 2, 3, 4, 5, 3, 1});
		mStack.execute(new RollCommand());
		assertStack(new int[]{0, 1, 2, 5, 3, 4});
	}
	
	public void testRoll7(){
		fillupStack(   new int[]{0, 1, 2, 3, 4, 5, 3, -1});
		mStack.execute(new RollCommand());
		assertStack(new int[]{0, 1, 2, 4, 5, 3});
	}
	
	public void testRoll8(){
		fillupStack(   new int[]{0, 1, 2, 3, 4, 5, 3, -2});
		mStack.execute(new RollCommand());
		assertStack(new int[]{0, 1, 2, 5, 3, 4});
	}	
	
	public void testRoll2(){
		fillupStack(new int[]{10, 100, 108, 108, 3, 3, 3, 2});
		mStack.execute(new RollCommand());
		assertStack(new int[]{10, 100, 108,   3, 3, 108});
	}	

}
